#include <unistd.h>
#include <iostream>
using namespace std;
#include <string>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
const int basesize = 1024;
const int gnum = 64;
const int envsize = 100;


int gargc = 0;
char* gargv[gnum];
char* genv[envsize];




char * filename;
int redirnum = 0;

char pwdenv[basesize];
char cwdarr[basesize];


#define Tracespace(pos) do { while((*pos) == ' ' && (*pos) != '\0')(pos)++;} while(0)

string GetUName()
{
    char* name = getenv("USER");
    if (name == nullptr) return "NONE";
    return name;
}

//修改pwd
void UpdateEnv(char* chpwd)
{
    for (int i = 0; genv[i]; i++)
    {
        if (strncmp("PWD",genv[i],3) == 0)
        {
            snprintf(genv[i],basesize,"PWD=%s",chpwd);
            return;
        }
    }
}
string GetPwd()
{
    if (!getcwd(cwdarr,basesize)) return "NONE";
    snprintf(pwdenv,basesize,"PWD=%s",cwdarr);
    UpdateEnv(cwdarr); //维护环境变量
    return cwdarr;
}

string GetHostname()
{
    char* hostname = getenv("HOSTNAME");
    if (hostname == nullptr) return "NONE";
    return hostname;
}


bool GetCmdLine(char* cmdline)
{
   char* ret  = fgets(cmdline,basesize,stdin);
   if (ret == nullptr || strlen(cmdline) == 1) return false;
   cmdline[strlen(cmdline)-1] = '\0'; //消除最后一个\n
   return true;
}

void PrinfCmdLine()
{
    string _path = GetPwd();
    if (_path != "/")
    {
        _path = _path.substr(_path.rfind("/")+1);
    }
    printf("[%s@%s %s]# ",GetUName().c_str(),GetHostname().c_str(),_path.c_str());
    fflush(stdout);
}

//特殊判断重定向
//"ls -a -l -n > filename" > "ls -a -l -n" + "filename"
void Judgeredir(char* cmdline)
{
    int end = strlen(cmdline);
    while(end >= 0)
    {
         if (cmdline[end] == '>') 
         {
             if ((end > 0 && cmdline[end-1] != '>') || (end == 0 && cmdline[end] == '>')) //普通重定向
             {
                 cmdline[end] = 0;
                 filename = cmdline + end + 1;
                 Tracespace(filename);
                 redirnum = 1; //1代表普通重定向
                return ;
             }
             else if (end > 0 && cmdline[end-1] == '>')  //追加重定向
             {
                 cmdline[end-1] = 0;
                 filename = cmdline + end + 1;
                 Tracespace(filename);
                 redirnum = 2; //2代表追加重定向
                return ; 
             }
         }
         else  if(cmdline[end] == '<') 
         {
            cmdline[end] = 0;
            filename = cmdline + end + 1;
            Tracespace(filename);
            redirnum = 3; //3代表输入重定向
            return ;
         }
        else end--;
     }
}

bool ParseCmdLine(char* cmdline)
{
    redirnum = 0;
    filename = nullptr;
    gargc = 0;
    memset(gargv,0,sizeof(gargv));
    const char* sep = " ";
   Judgeredir(cmdline);
    char* tmp = strtok(cmdline,sep);

    if (tmp == nullptr && !redirnum) return false; //只有当没有重定向并且没有输入指令的情况才可以退出
    else if(tmp == nullptr && redirnum)
    {
        return true; //就算只有单个 > + filename，那也要清空文件，所以这种情况也需要特殊处理
    }

    gargv[gargc] = (char*)malloc(strlen(tmp) + 1);
    strcpy(gargv[gargc],tmp);
    gargc++;
    while((bool)(tmp = strtok(nullptr,sep)))
    {
        gargv[gargc] = (char*)malloc(strlen(tmp)+1);
        strcpy(gargv[gargc],tmp);
        gargc++;
    }
    gargv[gargc] = nullptr;
    return true;
}

void ExcuteCmdline()
{
    pid_t id = fork();
    if (id == 0)
    {
        if (redirnum && filename  == nullptr) 
        {
            printf("%s\n","syntax error near unexpected token newline");
            exit(1);
        }
        if (redirnum == 1) //普通重定向
        {
            int fd = open(filename,O_CREAT | O_TRUNC | O_WRONLY,0666);       
            dup2(fd,1);
            if (!gargc) 
            {
                exit(0);
            }
        }
        else if(redirnum == 2) //追加重定向
        {

            int fd = open(filename,O_CREAT | O_APPEND | O_WRONLY,0666);       
            dup2(fd,1);
            if (!gargc) 
            {
                exit(0);
            }   
        }
        else if(redirnum == 3) //输入重定向
        {

            int fd = open(filename,O_RDONLY,0666);
            if (fd < 0) 
            {
                printf("No such file or directory\n");
                exit(2);
            }
            dup2(fd,0);
        }
        if (execvpe(gargv[0],gargv,genv) < 0)
        {
            printf("commend not found\n");
            exit(-1);
        }
        exit(0);
    }
    int status;
    pid_t w = waitpid(id,&status,0);
    if(w < 0)
    {
        printf("等待失败!\n");
    }
}

void AddEnv(char* newenv)
{
    int index = 0;
    while(genv[index])
    {
        ++index;
    }
    genv[index] = (char*)malloc(strlen(newenv)+1);
    strcpy(genv[index],newenv);
}


void PrintEnv()
{
    int index = 0;
    while(genv[index])
    {
        printf("%s\n", genv[index]);
        index++;
    }
}


bool CheckAndExcuteCmdline()
{
    if (strcmp("cd",gargv[0]) == 0)
    {
        if (gargc == 2)
        {
            if (chdir(gargv[1]) == -1) cout << "No Such file or directory!" << endl; 
            return true;
        }
    }
    else if(strcmp("export",gargv[0]) == 0)
    {
        if (gargc == 2)
        {
            AddEnv(gargv[1]);
            return true;
        }
    }
    else if(strcmp("env",gargv[0]) == 0)
    {
        if (gargc == 1)
        {
            PrintEnv();
            return true;
        }
    }

    return false;
}

void InitialEnv()
{
    extern char** environ;
    int index  = 0;
    while(environ[index])
    {
        genv[index] = (char*)malloc(strlen(environ[index])+1);
        strcpy(genv[index],environ[index]);
        index++;
    }
    genv[index] = nullptr;
}

int main()
{
    InitialEnv();
    char cmdline[basesize];
   //[xhm@iZ2zee7b26b1g3ujcquk70Z newshell]$ 
   while(true)
   {
        //1.打印命令行
        PrinfCmdLine();
        //2.命令行输入
        if(!GetCmdLine(cmdline))
        {
            continue;
        }
        //3.解析命令行
        if (!ParseCmdLine(cmdline)) continue;
        if(gargc && CheckAndExcuteCmdline()) //处理内键命令
        {
            continue;
        }
        //4.运行程序
        ExcuteCmdline();
   }
    return 0;
}
